package logger

import (
	"ginfwk/infrastructure/component/config"
	"go.uber.org/zap"
	"gopkg.in/natefinch/lumberjack.v2"
	"os"
	"path"
	"strings"
)
import "go.uber.org/zap/zapcore"

const RequestLevel = zapcore.DebugLevel - 1
const SqlLevel = zapcore.DebugLevel - 2

var zapLogger *zap.SugaredLogger

func InitLogger(cfg *config.Logger) {
	// writer
	debugLogWriter := getLevelLogWriter(cfg, zap.DebugLevel)
	infoLogWriter := getLevelLogWriter(cfg, zap.InfoLevel)
	warnLogWriter := getLevelLogWriter(cfg, zap.WarnLevel)
	errorLogWriter := getLevelLogWriter(cfg, zap.ErrorLevel)
	requestLogWriter := getLogWriter(cfg, getLogFileName(cfg, "request"))
	slqLogWriter := getLogWriter(cfg, getLogFileName(cfg, "sql"))

	// encoder
	logEncoder := getEncoder()

	// core
	sqlCore := zapcore.NewCore(logEncoder, slqLogWriter, equalPriority(SqlLevel))
	requestCore := zapcore.NewCore(logEncoder, requestLogWriter, equalPriority(RequestLevel))
	debugCore := zapcore.NewCore(logEncoder, debugLogWriter, equalPriority(zap.DebugLevel))
	infoCore := zapcore.NewCore(logEncoder, infoLogWriter, equalPriority(zap.InfoLevel))
	warnCore := zapcore.NewCore(logEncoder, warnLogWriter, equalPriority(zap.WarnLevel))
	errorCore := zapcore.NewCore(logEncoder, errorLogWriter, highPriority(zap.ErrorLevel))
	core := zapcore.NewTee(sqlCore, requestCore, debugCore, infoCore, warnCore, errorCore)

	zLog := zap.New(
		core,
		zap.AddCaller(),
		zap.AddStacktrace(zap.ErrorLevel),
	)
	zapLogger = zLog.Sugar()
}

func Default() *zap.SugaredLogger {
	return zapLogger
}

func Clone(opts ...zap.Option) *zap.SugaredLogger {
	return zapLogger.WithOptions(opts...)
}

func getLogWriter(cfg *config.Logger, filename string) zapcore.WriteSyncer {
	lumberJackLogger := &lumberjack.Logger{
		Filename:   path.Join(cfg.Dir, filename),
		MaxSize:    cfg.MaxSize, // megabytes
		MaxBackups: cfg.MaxBackups,
		MaxAge:     cfg.MaxAge,    //days
		Compress:   cfg.Compress,  // disabled by default
		LocalTime:  cfg.LocalTime, // false by default
	}
	syncer := []zapcore.WriteSyncer{
		zapcore.AddSync(lumberJackLogger),
	}
	if cfg.Debug {
		syncer = append(syncer, zapcore.AddSync(os.Stdout))
	}
	return zapcore.NewMultiWriteSyncer(syncer...)
}

func getLogFileName(cfg *config.Logger, levelName string) string {
	filename := cfg.Filename
	fileExt := path.Ext(filename)
	return strings.TrimSuffix(filename, fileExt) + "." + levelName + fileExt
}

func getLevelLogWriter(cfg *config.Logger, level zapcore.Level) zapcore.WriteSyncer {
	return getLogWriter(cfg, getLogFileName(cfg, level.String()))
}

func getEncoder() zapcore.Encoder {
	encoderConfig := zap.NewProductionEncoderConfig()
	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
	encoderConfig.EncodeLevel = capitalLevelEncoder
	return zapcore.NewJSONEncoder(encoderConfig)
}

func highPriority(standard zapcore.Level) zap.LevelEnablerFunc {
	return func(lev zapcore.Level) bool {
		return lev >= standard
	}
}

func equalPriority(standard zapcore.Level) zap.LevelEnablerFunc {
	return func(lev zapcore.Level) bool {
		return lev == standard
	}
}

// lowPriority
func lowPriority(standard zapcore.Level) zap.LevelEnablerFunc {
	return zap.LevelEnablerFunc(func(lev zapcore.Level) bool {
		return lev <= standard
	})
}

func capitalLevelEncoder(lev zapcore.Level, enc zapcore.PrimitiveArrayEncoder) {
	switch lev {
	case RequestLevel:
		enc.AppendString("REQUEST")
	case SqlLevel:
		enc.AppendString("SQL")
	default:
		zapcore.CapitalLevelEncoder(lev, enc)
	}
}
